﻿namespace NetFx.SMIL.Definitions
{
  using System;
  using System.Text;
  using System.Collections;
  using System.Collections.Generic;

  using System.Reflection;
  using System.Reflection.Emit;
  using System.Runtime.InteropServices;

  using System.Diagnostics;
  using System.Diagnostics.CodeAnalysis;
  using System.Diagnostics.SymbolStore;
  using System.Security;
  using System.Security.Permissions;

  using NetFx.MSIL;
  using NetFx.SMIL.References;

  public class MethodDef : MemberDef
  {
    #region Statics
    public static string ConstructorName = ".ctor";

    public static MethodDef of(MethodBase method,bool disassemble)
    {
      return new MethodDef(method, disassemble);
    }
    public static MethodDef of(string name)
    {
      return new MethodDef(name);
    }
    #endregion
    #region Fields
    protected MethodAttributes attributes = MethodAttributes.PrivateScope;
    protected string dllName = null;
    protected string entryName =null;
    protected TypeRef returnType = null;
    protected bool initLocals = true; //应保证此字段为true，否则Assembly无法通过验证。
    protected bool isPInvoke = false;
    protected bool isConstructor = false;
    protected bool isGenericMethodDefinition = false;
    protected bool hasGenericArguments = false;
    protected int maxStack = 0;
    protected MethodBase mbase = null;
    protected MethodBody body = null;
    protected CharSet nativeCharSet = CharSet.Unicode;
    protected CallingConventions callingConvertion = CallingConventions.Any;
    protected CallingConvention nativeCallingConvertion = CallingConvention.StdCall;
    protected List<ParameterDef> parameters = null;
    protected List<VariableDef> variables = null;
    protected List<LabelDef> labels = null;
    protected List<SmilOperation> operations = null;
    protected byte[] ilCodes = null;
    protected int localSignatureToken = 0;
    #endregion
    #region Properties
    public virtual MethodBase Base
    {
      get
      {
        return this.mbase;
      }
    }
    public virtual MethodBody Body
    {
      get
      {
        return this.body;
      }
    }
    public override DefType DefinitionType
    {
      get
      {
        return DefType.Method;
      }
    }
    public virtual MethodAttributes Attributes
    {
      get
      {
        return this.attributes;
      }
      set
      {
        this.attributes = value;
      }
    }
    public virtual bool IsPInvoke
    {
      get
      {
        return this.isPInvoke;
      }
      set
      {
        this.isPInvoke = value;
      }
    }
    public virtual bool IsConstructor
    {
      get
      {
        return this.isConstructor;
      }
      set
      {
        this.isConstructor = value;
      }
    }
    public virtual string DllName
    {
      get
      {
        return this.dllName;
      }
      set
      {
        this.dllName = value;
      }
    }
    public virtual string EntryName
    {
      get
      {
        return this.entryName;
      }
      set
      {
        this.entryName = value;
      }
    }
    public virtual TypeRef ReturnType
    {
      get
      {
        return this.returnType;
      }
      set
      {
        this.returnType = value;
      }
    }
    public virtual List<ParameterDef> Parameters
    {
      get
      {
        return this.parameters == null 
          ? this.parameters = new List<ParameterDef>()
          : this.parameters;
      }
    }
    public virtual List<VariableDef> Variables
    {
      get
      {
        return this.variables == null
          ? this.variables = new List<VariableDef>()
          : this.variables;
      }
    }
    public virtual List<LabelDef> Labels
    {
      get
      {
        return this.labels == null
          ? this.labels = new List<LabelDef>()
          : this.labels;
      }
    }
    public virtual List<SmilOperation> Operations
    {
      get
      {
        return this.operations == null ?
          this.operations = new List<SmilOperation>() :
          this.operations;
      }
    }
    public virtual CharSet NativeCharSet
    {
      get
      {
        return this.nativeCharSet;
      }
      set
      {
        this.nativeCharSet = value;
      }
    }
    public virtual CallingConvention NativeCallingConvertion
    {
      get
      {
        return this.nativeCallingConvertion;
      }
      set
      {
        this.nativeCallingConvertion = value;
      }
    }
    public virtual CallingConventions CallingConvertion
    {
      get
      {
        return this.callingConvertion;
      }
      set
      {
        this.callingConvertion = value;
      }
    }
    public virtual byte[] ILCodes
    {
      get
      {
        return this.ilCodes;
      }
      set
      {
        this.ilCodes = value;
      }
    }
    public virtual int MaxStack
    {
      get
      {
        return this.maxStack;
      }
      set
      {
        this.maxStack = value >=0 ? value: 0;
      }
    }
    public virtual bool InitLocals
    {
      get
      {
        return this.initLocals;
      }
      set
      {
        this.initLocals = value;
      }
    }
    public virtual int LocalSignatureToken
    {
      get
      {
        return this.localSignatureToken;
      }
    }
    #region Attribute Flags
    public virtual bool HasGenericArguments
    {
      get
      {
        return this.hasGenericArguments;
      }
      set
      {
        this.hasGenericArguments = value;
      }
    }
    public virtual bool IsAbstract
    {
      get
      {
        return ((this.Attributes & MethodAttributes.Abstract) != MethodAttributes.PrivateScope);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.Abstract;
        }
        else
        {
          this.attributes &= (~MethodAttributes.Abstract);
        }
      }
    }
    public virtual bool IsAssembly
    {
      get
      {
        return ((this.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Assembly);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.Assembly;
        }
        else
        {
          this.attributes = this.ClearMethodAccess(this.attributes);
        }
      }
    }
    public virtual bool IsFamily
    {
      get
      {
        return ((this.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Family);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.Family;
        }
        else
        {
          this.attributes = this.ClearMethodAccess(this.attributes);
        }
      }
    }
    public virtual bool IsFamilyAndAssembly
    {
      get
      {
        return ((this.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamANDAssem);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.FamANDAssem;
        }
        else
        {
          this.attributes = this.ClearMethodAccess(this.attributes);
        }
      }
    }
    public virtual bool IsFamilyOrAssembly
    {
      get
      {
        return ((this.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.FamORAssem);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.FamORAssem;
        }
        else
        {
          this.attributes = this.ClearMethodAccess(this.attributes);
        }
      }
    }
    public virtual bool IsFinal
    {
      get
      {
        return ((this.Attributes & MethodAttributes.Final) != MethodAttributes.PrivateScope);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.Final;
        }
        else
        {
          this.attributes &= (~MethodAttributes.Final);
        }
      }
    }
    public virtual bool IsHideBySig
    {
      get
      {
        return ((this.Attributes & MethodAttributes.HideBySig) != MethodAttributes.PrivateScope);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.HideBySig;
        }
        else
        {
          this.attributes &= (~MethodAttributes.HideBySig);
        }
      }
    }
    public virtual bool IsPrivate
    {
      get
      {
        return ((this.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Private);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.Private;
        }
        else
        {
          this.attributes = this.ClearMethodAccess(this.attributes);
        }
      }
    }
    public virtual bool IsPublic
    {
      get
      {
        return ((this.Attributes & MethodAttributes.MemberAccessMask) == MethodAttributes.Public);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.Public;
        }
        else
        {
          this.attributes = this.ClearMethodAccess(this.attributes);
        }
      }
    }
    public virtual bool IsSpecialName
    {
      get
      {
        return ((this.Attributes & MethodAttributes.SpecialName) != MethodAttributes.PrivateScope);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.SpecialName;
        }
        else
        {
          this.attributes &= (~MethodAttributes.SpecialName);
        }
      }
    }
    public virtual bool IsStatic
    {
      get
      {
        return ((this.Attributes & MethodAttributes.Static) != MethodAttributes.PrivateScope);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.Static;
        }
        else
        {
          this.attributes &= (~MethodAttributes.Static);
        }
      }
    }
    public virtual bool IsVirtual
    {
      get
      {
        return ((this.Attributes & MethodAttributes.Virtual) != MethodAttributes.PrivateScope);
      }
      set
      {
        if (value)
        {
          this.attributes |= MethodAttributes.Virtual;
        }
        else
        {
          this.attributes &= (~MethodAttributes.Virtual);
        }
      }
    }
    public virtual bool IsGenericMethodDefinition
    {
      get
      {
        return this.isGenericMethodDefinition;
      }
    }
    #endregion
    #endregion
    #region Constructors
    public MethodDef(MethodBase method, bool disassemble)
      : base(String.Empty)
    {

      this.OnReflecting(method, disassemble);
      
    }
    public MethodDef(string name)
      : base(name)
    {
      this.IsConstructor = (name == null || name == string.Empty || (name.ToLower() == ConstructorName.ToLower()));
    }
    #endregion
    #region Methods
    #region Publics
    public virtual ParameterDef DefineParameter(string name)
    {
      return this.AddParameter(ParameterDef.of(name));
    }
    public virtual ParameterDef FindParameter(string name)
    {
      if (!string.IsNullOrEmpty(name) && this.parameters != null)
      {
        foreach (ParameterDef parameter in this.parameters)
        {
          if (parameter != null && parameter.Name == name)
          {
            return parameter;
          }
        }
      }
      return null;
    }
    public virtual ParameterDef RemoveParameter(string name)
    {
      return this.RemoveParameter(this.FindParameter(name));
    }
    public virtual VariableDef DefineVariable(string name)
    {
      return this.AddVariable(VariableDef.of(name));
    }
    public virtual VariableDef FindVariable(string name)
    {
      if (!string.IsNullOrEmpty(name) && this.variables != null)
      {
        foreach (VariableDef variable in this.variables)
        {
          if (variable != null && variable.Name == name)
          {
            return variable;
          }
        }
      }
      return null;
    }
    public virtual VariableDef RemoveVariable(string name)
    {
      return this.RemoveVariable(this.FindVariable(name));
    }
    public virtual LabelDef DefineLabel(string name)
    {
      return this.AddLabel(LabelDef.of(name));
    }
    public virtual LabelDef FindLabel(string name)
    {
      if (!string.IsNullOrEmpty(name) && this.labels != null)
      {
        foreach (LabelDef label in this.labels)
        {
          if (label != null && label.Name == name)
          {
            return label;
          }
        }
      }
      return null;
    }
    public virtual LabelDef RemoveLabel(string name)
    {
      return this.RemoveLabel(this.FindLabel(name));
    }
    public virtual void ClearLabels()
    {
      if (this.labels != null)
      {
        this.labels.Clear();
      }
    }
    #endregion
    #region Internals
    protected virtual ParameterDef AddParameter(ParameterDef parameter)
    {
      if (parameter != null)
      {
        parameter.Parent = this;
        this.Parameters.Add(parameter);
      }
      return parameter;
    }
    protected virtual ParameterDef RemoveParameter(ParameterDef parameter)
    {
      if (parameter != null)
      {
        parameter.Parent = null;
        this.Parameters.Remove(parameter);
      }
      return parameter;
    }
    protected virtual VariableDef AddVariable(VariableDef variable)
    {
      if (variable != null)
      {
        variable.Parent = this;
        this.Variables.Add(variable);
      }
      return variable;
    }
    protected virtual VariableDef RemoveVariable(VariableDef variable)
    {
      if (variable != null)
      {
        variable.Parent = null;
        this.Variables.Remove(variable);
      }
      return variable;
    }
    protected virtual LabelDef AddLabel(LabelDef label)
    {
      if (label != null)
      {
        label.Parent = this;
        this.Labels.Add(label);
      }
      return label;
    }
    protected virtual LabelDef RemoveLabel(LabelDef label)
    {
      if (label != null)
      {
        label.Parent = null;
        this.Labels.Remove(label);
      }
      return label;
    }
    protected override AnyGenerator OnCompleting(AnyGenerator generator)
    {
      if (generator is MethodGenerator)
      {
        //Setup ILCode for method.
        (generator as MethodGenerator).ILCodes = this.ILCodes;

        //TODO: A lot of attributes to apply!
      }
      return base.OnCompleting(generator);
    }
    protected override AnyGenerator OnDefining(AnyGenerator container)
    {
      MethodGenerator methodGenerator = null;
      if (container is TypeGenerator)
      {
        if (this.IsConstructor)
        {
          if (this.IsPInvoke)
          {
            throw new InvalidOperationException("Constructor should not be defined as PInvoke method!");
          }
          else
          {
            methodGenerator = ((TypeGenerator)container).DefineConstructor(
              this.Attributes, this.CallingConvertion, this.GetParameterTypes());
          }
        }
        else
        {
          if (this.IsPInvoke)
          {
            methodGenerator = ((TypeGenerator)container).DefinePInvokeMethod(
              this.Name, this.DllName, this.EntryName, this.Attributes, this.callingConvertion, this.ReturnType.ReflectInfo, this.GetParameterTypes(), this.NativeCallingConvertion, this.NativeCharSet);
          }
          else
          {
            methodGenerator = ((TypeGenerator)container).DefineMethod(
              this.Name, this.Attributes, this.CallingConvertion, this.ReturnType.ReflectInfo, this.GetParameterTypes());
          }
        }
      }
      else if (container is ModuleGenerator)
      {
        if (this.IsConstructor)
        {
          throw new InvalidOperationException("Constructor should not be defined at module level!");
        }
        else
        {
          if (this.IsPInvoke)
          {
            methodGenerator = ((ModuleGenerator)container).DefinePInvokeMethod(
              this.Name, this.DllName, this.EntryName, this.Attributes,
              this.callingConvertion, this.ReturnType.ReflectInfo, this.GetParameterTypes(), this.NativeCallingConvertion, this.NativeCharSet);
          }
          else
          {
            methodGenerator= ((ModuleGenerator)container).DefinePlainMethod(
              this.Name, this.Attributes, this.CallingConvertion,
              this.ReturnType.ReflectInfo, this.GetParameterTypes());
          }
        }
        if (methodGenerator != null)
        {
          using (SmilCodeCompositor compositor = SmilCodeCompositor.of(methodGenerator.MethodBuilder))
          {
            if (compositor.Composite(this.Operations))
            {
              //Successfully composited
            }
            else
            {
              //Error when compositing.
            }
          }
        }
      }
      return base.OnDefining(container);
    }
    protected virtual Type[] GetParameterTypes()
    {
      Type[] types = new Type[this.Parameters.Count];
      for (int i = 0; i < types.Length; i++)
      {
        ParameterDef header = this.Parameters[i] as ParameterDef;
        if (header != null)
        {
          types[i] = header.ParameterType.ReflectInfo;
        }
      }
      return types;
    }

    protected virtual MethodAttributes ClearMethodAccess(MethodAttributes attrib)
    {
      return attrib & (~MethodAttributes.MemberAccessMask);
    }
    protected virtual MethodAttributes ClearReserved(MethodAttributes attrib)
    {
      return attrib & (~MethodAttributes.ReservedMask);
    }
    protected override ReflectDef OnReflecting(object source)
    {
      return this.OnReflecting(source, true);
    }
    protected virtual ReflectDef OnReflecting(object source,bool disassemble)
    {
      if (source is MethodBase)
      {
        MethodBase mb = source as MethodBase;

        this.mbase = mb;

        this.attributes = mb.Attributes;
        //TODO: NEED TO IMPROVE!
        this.dllName = string.Empty;
        this.entryName = string.Empty;

        this.body = mb.GetMethodBody();

        if (this.body != null)
        {
          this.ilCodes = this.body.GetILAsByteArray();
          this.maxStack = this.body.MaxStackSize;
          this.localSignatureToken = this.body.LocalSignatureMetadataToken;
          this.initLocals = this.body.InitLocals;

          List<LocalVariableInfo> locals = new List<LocalVariableInfo>(this.body.LocalVariables);
          {
            this.ReloadVariables(locals.ToArray(), this.Variables);
          }
          locals.Clear();
          locals = null;
          if (disassemble)
          {
            using (SmilCodeAnalyser analyser = SmilCodeAnalyser.of(mb))
            {
              if (analyser.Parse(this.Operations))
              {
                //Successfully loaded
              }
              else
              {
                //Errors when parsing.
              }
            }
          }
        }

        if (mb is MethodInfo)
        {
          MethodInfo mi = mb as MethodInfo;
          this.returnType = TypeRef.of(mi.ReturnType);
          this.callingConvertion = mi.CallingConvention;
          //TODO: NEED TO FINISH!
        }
        this.ReloadParameters(mb.GetParameters(), this.Parameters);
      }
      return base.OnReflecting(source);
    }

    protected override void DisposeList(bool disposing)
    {
      if (disposing)
      {
        this.DisposeList(this.Parameters);
        this.DisposeList(this.Variables);
        this.DisposeList(this.Labels);
      }
    }
    protected override void OnBreakingReference()
    {
      this.Break(this.ReturnType);
      this.Break(this.Parameters);
      this.Break(this.Variables);
      this.Break(this.Labels);
      base.OnBreakingReference();
    }
    #endregion
    #endregion
  }
}
